--- 模块功能：MCP23017
--- Author JWL  参考STM32  https://blog.csdn.net/lnniyunlong99/article/details/107728094

--module(..., package.seeall)

require "utils"
require "sys"
require "pins"
-- MCP32017驱动的LED,键盘,蜂鸣器,电机等
    --require "Led_Key_IO_etc"    


local MCP23017_IODIR 	    =	0x00                      
local MCP23017_IPOL 	    =	0x02                      
local MCP23017_GPINTEN 	    =	0x04                      
local MCP23017_DEFVAL 	    =	0x06                      
local MCP23017_INTCON 	    =	0x08                      
local MCP23017_IOCON 	    =	0x0A                      
local MCP23017_GPPU 	    =	0x0C                      
local MCP23017_INTF 	    =	0x0E                      
local MCP23017_INTCAP 	    =	0x10                      
local MCP23017_GPIO 	    =	0x12                      
local MCP23017_OLAT 	    =	0x14                      
local MCP23017_PORTA	    =	0x00   -- 0: 端口A                   
local MCP23017_PORTB	    =	0x01   -- 1：端口B                   
local INPUT				    =	0x01                            
local OUTPUT			    =	0x00                          
local ENABLE			    =   0x01                          
local DISABLE		        =   0x00                          
local SET				    =   0x01                            
local RESET				    =   0x00                            
local POLARITY_REVERSE	    =	0x01                      
local POLARITY_NON_REVERSE  =	0x00    

local PIN0				    =   0x01                            
local PIN1				    =	0x02                            
local PIN2				    =	0x04                            
local PIN3				    =	0x08                            
local PIN4				    =	0x10                            
local PIN5				    =	0x20                            
local PIN6				    =	0x40                            
local PIN7				    =	0x80                     

local ALLPIN			    =	0xFF         

local DIS_INTERRUPT			=   0x00	                      
local HIGHLEVEL_INTERRUPT	=	0x01	                  
local LOWLEVEL_INTERRUPT	=	0x02	                  
local CHANGE_INTERRUPT		=   0x03	                    
local INTA_INTB_CONJUNCTION	=   0x00	                  
local INTA_INTB_INDEPENDENT	=   0x01	                  
local HWA_EN				=	0x00	                        
local HWA_DIS				=	0x01	                        
local INT_OD				=	0x00	                        
local INT_PUSHPULL_HIGH	    =	0x01	                    
local INT_PUSHPULL_LOW	    =	0x02	                    
local I2C_READ              =   0x01   
local I2C_WRITE             =   0x00   
local DEV_ID                =   0x20 		-- MCP23017的地址
local I2C_ID                =   0x02        -- I2C的端口号

-- 打开MCP23017的IIC接口
local function I2C_Open(id)
    if i2c.setup(id, i2c.SLOW) ~= i2c.SLOW then
        log.error("MCP23017", "I2C.init is: fail")  -- 有多个I2C设备,因此I2C可能已经被打开了,再次打开会出错
        --i2c.close(id)     -- 有多个I2C设备,因此I2C打开一次,不要关闭
        return
    else
        log.info("MCP23017", "I2C.init is: succeed")
    end
    return i2c.SLOW
end

-- 向芯片地址为deviceAddr的MCP23017芯片发送数据content
local function I2C_Write_Byte_MCP23017(deviceAddr, regAddress,content)
    local i2cslaveaddr = bit.bor(DEV_ID,  deviceAddr)
    i2c.send(I2C_ID, i2cslaveaddr, {regAddress,content})
end

-- 向芯片地址为deviceAddr的MCP23017芯片，读取1字节数据
local function I2C_Read_Byte_MCP23017(deviceAddr,  regAddress)
    local i2cslaveaddr =bit.bor(DEV_ID,  deviceAddr)
    i2c.send(I2C_ID, i2cslaveaddr, regAddress)
    return i2c.recv(I2C_ID, i2cslaveaddr, 1):byte(1)
end

-- MCP23017芯片初始化
-- intab 中断输出端口控制，默认不关联
-- hwa  SPI硬件地址允许，默认1，采用硬件地址（hwa==HWA_DIS，屏蔽硬件地址）
-- o: INT端口是开漏输出（INT_PUSHPULL_HIGH，INT_PUSHPULL_L）
-- port： 0，端口A；1，端口B；
local function MCP23017_INIT(deviceAddr,intab,hwa,port, o)
	local state,res;

	--首先设置其他位的默认状态
	state = 0X22;		--001011 10,BANK = 0,默认不关联AB（bit = 0）,禁用顺序操作,使能变化率控制、使能硬件地址,开漏输出

    -- intab 中断输出端口控制，默认不关联
	if intab==INTA_INTB_CONJUNCTION then
        state = bit.bor(state,0x40)  --	state |= 0x40;
    end

    -- hwa  SPI硬件地址允许，默认1，采用硬件地址
	if hwa==HWA_DIS then
        state = bit.band( state, bit.bnot(0x08))  -- state &= (~0x08);
    end

    -- o: INT端口是开漏输出（INT_PUSHPULL_HIGH，INT_PUSHPULL_L）
	if o==INT_PUSHPULL_HIGH then
        state = bit.band(state, bit.bnot(0x04))   -- state &= (~0x04);
		state = bit.bor(state,0x02)               -- state |= 0x02;
    end
	if o==INT_PUSHPULL_LOW then
        state = bit.band(state, bit.bnot(0x04))   -- state &= (~0x04);
        state = bit.band(state, bit.bnot(0x02))   -- state &= (~0x02);
    end
	--写回方向寄存器
	res = I2C_Write_Byte_MCP23017(deviceAddr,MCP23017_IOCON +port,state)
	return res;
end

-- 配置端口的输入输出属性
-- deviceAddr： MCP23017地址
-- port： 0，端口A； 1： 端口B
-- pin：每一个bit代表需要配置的端口管脚
-- dir： 端口方向，INPUT为输入
local function MCP23017_IO_DIR(deviceAddr,port,pin,dir)

	local portState = 0x00
	--首先读取当前端口方向的配置状态
	--因为B端口的地址比A端口的寄存器的地址都是大1，所以采用+的技巧切换寄存器
	--portState =I2C_Read_Byte_MCP23017(deviceAddr,MCP23017_IODIR+port);

	if dir==INPUT then
		portState = bit.bor(portState, pin)             --portState |= pin;
    else
        portState = bit.band(portState, bit.bnot(pin))	--portState &= (~pin);
	end

	--写回方向寄存器
	I2C_Write_Byte_MCP23017(deviceAddr,MCP23017_IODIR+port,portState)
	
end

-- 配置端口的上拉属性
local function MCP23017_IO_PU(deviceAddr,port,pin,pu)
	local portState = 0x00
	if pu==ENABLE then
        portState = bit.bor(portState,pin)  --	portState |= pin;
    else
        portState = bit.band(portState, bit.bnot(pin))  	--portState &= (~pin);
	end
	I2C_Write_Byte_MCP23017(deviceAddr,MCP23017_GPPU+port,portState)
end



local function MCP23017_IO_INT(deviceAddr, port, pin, intKind)
    local portState_GPINTEN = 0 -- 中断使能寄存器
	local portState_DEFVAL  = 0 -- 中断默认值寄存器
	local portState_INTCON  = 0 -- 中断控制寄存器
	
	--首先读取当前配置状态
	--因为B端口的地址比A端口的寄存器的地址都是大1，所以采用+的技巧切换寄存器
	portState_GPINTEN = I2C_Read_Byte_MCP23017(deviceAddr,MCP23017_GPINTEN +port)		
	portState_DEFVAL  = I2C_Read_Byte_MCP23017(deviceAddr,MCP23017_DEFVAL  +port)
	portState_INTCON  = I2C_Read_Byte_MCP23017(deviceAddr,MCP23017_INTCON  +port)

    log.info("portState_GPINTEN,portState_DEFVAL,portState_INTCON", portState_GPINTEN,portState_DEFVAL,portState_INTCON)
 
	--判断中断的类型
	--如果是关闭中断
	if intKind==DIS_INTERRUPT then
        portState_GPINTEN = bit.band(portState_GPINTEN,bit.bnot(pin))  -- portState_GPINTEN &= (~pin);	
    end
	--如果是变化中断
	if intKind==CHANGE_INTERRUPT then
		portState_GPINTEN = bit.bor(portState_GPINTEN, pin)             -- portState_GPINTEN |= pin;
        portState_INTCON  = bit.band(portState_INTCON, bit.bnot(pin))   -- portState_INTCON  &= (~pin);		
    end
	--如果是高电平中断
	if intKind==HIGHLEVEL_INTERRUPT then
		portState_GPINTEN = bit.bor(portState_GPINTEN, pin)             -- portState_GPINTEN |= pin;
		portState_INTCON  = bit.bor(portState_INTCON,  pin)              -- portState_INTCON  |= pin;
		portState_DEFVAL  = bit.band(portState_DEFVAL, bit.bnot(pin))   -- portState_DEFVAL  &= (~pin);		
    end
	--如果是低电平中断
	if intKind==LOWLEVEL_INTERRUPT then
		portState_GPINTEN = bit.bor(portState_GPINTEN, pin)             -- portState_GPINTEN |= pin;
		portState_INTCON  = bit.bor(portState_INTCON,  pin)              -- portState_INTCON  |= pin;
		portState_DEFVAL  = bit.bor(portState_DEFVAL,  pin)              -- portState_DEFVAL  |= pin;		
    end
	--写回寄存器
    I2C_Write_Byte_MCP23017(deviceAddr,MCP23017_GPINTEN+port,portState_GPINTEN)
    I2C_Write_Byte_MCP23017(deviceAddr,MCP23017_INTCON+port,portState_INTCON)
    I2C_Write_Byte_MCP23017(deviceAddr,MCP23017_DEFVAL+port,portState_DEFVAL)

end

-- 写端口
local function MCP23017_WRITE_GPIO(deviceAddr,port,val)	
	I2C_Write_Byte_MCP23017(deviceAddr,MCP23017_GPIO+port,val)
end

-- 读端口
local function MCP23017_READ_GPIO(deviceAddr,port)
	return I2C_Read_Byte_MCP23017(deviceAddr,MCP23017_GPIO+port)
end


--[[
local flag_interrupt=0

pins.setup(
   pio.P0_13,
   function(msg)
        flag_interrupt = 1

        log.info("flag_interrupt, msg", flag_interrupt, msg )
   end,
   pio.PULLUP
)
]]





--***********  MCP23017的芯片参数 *********************--

MCP23017={
    gap = 40,           -- 读写MCP23017的时间间隔8+40ms

    Chip1 ={
        addr    = 0,                    -- IIC芯片的地址000b,电机，继电器，手柄等
        dir     = { 0xf0,   0xff },     -- 【端口A，端口B】的输入输出属性，1: 输入，0：输出
        pullup  = { 0xff,0xff },        -- 1：上拉，0：没有上拉
        input   = { 0,   0 },           -- 读取端口值[A,B]
        output  = { 0x0,0 }             -- 端口输出值，这个是初始化值[A,B]
    },

    Chip2 ={
        addr    = 1,                    -- IIC芯片的地址001b，LED和蜂鸣器，拨码开关等
        dir     = { 0,   0x0f },        -- 【端口A，端口B】的输入输出属性，1: 输入，0：输出
        pullup  = { 0xff,0xff },        -- 1：上拉，0：没有上拉
        input   = { 0,   0 },           -- 读取端口值[A,B]
        output  = { 0xf0,0 }            -- 端口输出值[A,B],这个是初始化值 (开机关闭LED和beep)
    }
}


--***********  MCP23017的定时刷新线程(约50ms) *********************--


-- 要1分钟定时刷新IIC端口配置,防止芯片死机
sys.taskInit(function()

    log.info("MCP23017", "INIT..." )
    
    -- 打开I2C端口,可能有多个I2C设备,因此I2C可能已经被打开了,在此打开会出错,所以只打开一次
    sys.wait(2000)
    if I2C_ID then i2c.close(I2C_ID) end     -- 有多个I2C设备,因此I2C打开一次,不要关闭
    I2C_Open(I2C_ID)    sys.wait(5)

    --log.debug("MCP23017",json.encode(MCP23017))

    -- MCP23017的硬件地址和中断配置，先配置chip2，因为chip1的看门狗可能还没有喂过
    MCP23017_INIT(MCP23017.Chip2.addr, INTA_INTB_INDEPENDENT, HWA_EN, MCP23017_PORTA, INT_OD)  sys.wait(2)
    MCP23017_INIT(MCP23017.Chip2.addr, INTA_INTB_INDEPENDENT, HWA_EN, MCP23017_PORTB, INT_OD)  sys.wait(2)
    MCP23017_INIT(MCP23017.Chip1.addr, INTA_INTB_INDEPENDENT, HWA_EN, MCP23017_PORTA, INT_OD)  sys.wait(2)
    MCP23017_INIT(MCP23017.Chip1.addr, INTA_INTB_INDEPENDENT, HWA_EN, MCP23017_PORTB, INT_OD)  sys.wait(2)
    
    -- 配置A芯片的pin输入输出属性
    -- Addr： MCP23017地址
    -- port： 0，端口A； 1： 端口B
    -- pin：每一个bit代表需要配置的端口管脚
    -- dir： 端口方向，INPUT为输入
    MCP23017_IO_DIR(MCP23017.Chip1.addr, MCP23017_PORTA, MCP23017.Chip1.dir[1], INPUT)  sys.wait(2)
    MCP23017_IO_DIR(MCP23017.Chip1.addr, MCP23017_PORTB, MCP23017.Chip1.dir[2], INPUT)  sys.wait(2)

    MCP23017_IO_DIR(MCP23017.Chip2.addr, MCP23017_PORTA, MCP23017.Chip2.dir[1], INPUT)  sys.wait(2)
    MCP23017_IO_DIR(MCP23017.Chip2.addr, MCP23017_PORTB, MCP23017.Chip2.dir[2], INPUT)  sys.wait(2)

    -- 配置端口上拉
    MCP23017_IO_PU(MCP23017.Chip1.addr, MCP23017_PORTA, MCP23017.Chip1.pullup[1], ENABLE)   sys.wait(2)
    MCP23017_IO_PU(MCP23017.Chip1.addr, MCP23017_PORTB, MCP23017.Chip1.pullup[2], ENABLE)   sys.wait(2)

    MCP23017_IO_PU(MCP23017.Chip2.addr, MCP23017_PORTA, MCP23017.Chip2.pullup[1], ENABLE)   sys.wait(2)
    MCP23017_IO_PU(MCP23017.Chip2.addr, MCP23017_PORTB, MCP23017.Chip2.pullup[2], ENABLE)   sys.wait(2)

    log.info("MCP23017", "INIT OK" )
    --log.debug("MCP23017",json.encode(MCP23017))

    sys.wait(60000)        --1分钟初始化一次

end)



sys.taskInit(function()

    --[[
    log.info("MCP23017", "INIT..." )
    
    -- 打开I2C端口,可能有多个I2C设备,因此I2C可能已经被打开了,在此打开会出错,所以只打开一次
    sys.wait(2000)
    I2C_Open(I2C_ID)
    sys.wait(500)

    --log.debug("MCP23017",json.encode(MCP23017))

    -- MCP23017的硬件地址和中断配置，先配置chip2，因为chip1的看门狗可能还没有喂过
    MCP23017_INIT(MCP23017.Chip2.addr, INTA_INTB_INDEPENDENT, HWA_EN, MCP23017_PORTA, INT_OD)  sys.wait(100)
    MCP23017_INIT(MCP23017.Chip2.addr, INTA_INTB_INDEPENDENT, HWA_EN, MCP23017_PORTB, INT_OD)  sys.wait(100)
    MCP23017_INIT(MCP23017.Chip1.addr, INTA_INTB_INDEPENDENT, HWA_EN, MCP23017_PORTA, INT_OD)  sys.wait(100)
    MCP23017_INIT(MCP23017.Chip1.addr, INTA_INTB_INDEPENDENT, HWA_EN, MCP23017_PORTB, INT_OD)  sys.wait(100)
    
    -- 配置A芯片的pin输入输出属性
    -- Addr： MCP23017地址
    -- port： 0，端口A； 1： 端口B
    -- pin：每一个bit代表需要配置的端口管脚
    -- dir： 端口方向，INPUT为输入
    MCP23017_IO_DIR(MCP23017.Chip1.addr, MCP23017_PORTA, MCP23017.Chip1.dir[1], INPUT)  sys.wait(50)
    MCP23017_IO_DIR(MCP23017.Chip1.addr, MCP23017_PORTB, MCP23017.Chip1.dir[2], INPUT)  sys.wait(50)

    MCP23017_IO_DIR(MCP23017.Chip2.addr, MCP23017_PORTA, MCP23017.Chip2.dir[1], INPUT)  sys.wait(50)
    MCP23017_IO_DIR(MCP23017.Chip2.addr, MCP23017_PORTB, MCP23017.Chip2.dir[2], INPUT)  sys.wait(50)

    -- 配置端口上拉
    MCP23017_IO_PU(MCP23017.Chip1.addr, MCP23017_PORTA, MCP23017.Chip1.pullup[1], ENABLE)   sys.wait(50)
    MCP23017_IO_PU(MCP23017.Chip1.addr, MCP23017_PORTB, MCP23017.Chip1.pullup[2], ENABLE)   sys.wait(50)

    MCP23017_IO_PU(MCP23017.Chip2.addr, MCP23017_PORTA, MCP23017.Chip2.pullup[1], ENABLE)   sys.wait(50)
    MCP23017_IO_PU(MCP23017.Chip2.addr, MCP23017_PORTB, MCP23017.Chip2.pullup[2], ENABLE)   sys.wait(50)

    log.info("MCP23017", "INIT OK" )
    --log.debug("MCP23017",json.encode(MCP23017))
    ]]

    sys.wait(300)  --延时0.33秒钟,等待IIC接口完成初始化
    -- 定时读取MCP23017芯片的输入和输出,并存入表格
    while true do
        -- 刷新输出端口
        MCP23017_WRITE_GPIO(MCP23017.Chip1.addr, MCP23017_PORTA,MCP23017.Chip1.output[1])       sys.wait(1) 
        MCP23017_WRITE_GPIO(MCP23017.Chip1.addr, MCP23017_PORTB,MCP23017.Chip1.output[2])       sys.wait(1) 

        MCP23017_WRITE_GPIO(MCP23017.Chip2.addr, MCP23017_PORTA,MCP23017.Chip2.output[1])       sys.wait(1) 
        MCP23017_WRITE_GPIO(MCP23017.Chip2.addr, MCP23017_PORTB,MCP23017.Chip2.output[2])       sys.wait(1) 

        --读取端口的值
        MCP23017.Chip1.input[1] = MCP23017_READ_GPIO( MCP23017.Chip1.addr, MCP23017_PORTA )     sys.wait(1) 
        MCP23017.Chip1.input[2] = MCP23017_READ_GPIO( MCP23017.Chip1.addr, MCP23017_PORTB )     sys.wait(1) 
    
        MCP23017.Chip2.input[1] = MCP23017_READ_GPIO( MCP23017.Chip2.addr, MCP23017_PORTA )     sys.wait(1) 
        MCP23017.Chip2.input[2] = MCP23017_READ_GPIO( MCP23017.Chip2.addr, MCP23017_PORTB )     sys.wait(1) 

        sys.wait(MCP23017.gap)     

    end
end)